/*
    Author: Christopher Diggins
    License: MIT Licence 1.0
    Website: http://www.cdiggins.com
*/

#ifndef CVML_INSTRUCTIONS_HPP
#define CVML_INSTRUCTIONS_HPP

struct Subroutine;

struct Instruction 
{
    OpCodeEnum opcode;
    int operand;		
    int id;
    std::string comment;        
    Instruction* next;
    Instruction* prev;

    int NewId() {
        static int gid = 0;
        return gid++;
    }

	Instruction(OpCodeEnum code = op_noop, int arg = -1, const std::string& s = "")
		: opcode(code), operand(arg), id(NewId()), comment(s), next(NULL), prev(NULL)
    {
    }

    Instruction* AddInstruction(OpCodeEnum _opcode = op_noop, int _operand = -1, std::string _comment = "")
    {
        Instruction* tmp = next;
        next = new Instruction();
        next->next = tmp;
        next->opcode = _opcode;
        next->operand = _operand;
        next->comment = _comment;
        next->prev = this;
        return next;
    }

    Instruction* AddInstruction(OpCodeEnum _opcode, Subroutine* sub)
    {
		return AddInstruction(_opcode, reinterpret_cast<int>(sub));
	}

    Instruction* AddInstructionBefore(OpCodeEnum _opcode = op_noop, int _operand = -1, std::string _comment = "")
    {
        Instruction* tmp = prev;
        prev = new Instruction();
        prev->next = this;
        prev->opcode = _opcode;
        prev->operand = _operand;
        prev->comment = _comment;
        prev->prev = tmp;
        return prev;
    }

    Instruction* AddInstruction(const Instruction& x) 
    {
        return AddInstruction(x.opcode, x.operand, x.comment);
    }

    Instruction* AddInstructionBefore(const Instruction& x) 
    {
        return AddInstructionBefore(x.opcode, x.operand, x.comment);
    }

    void RemoveNext() {
        Instruction* tmp = next;
        next = next->next;
        if (next != NULL) 
            next->prev = this;
        delete(tmp);
    }

    int Len() {
        Instruction* tmp = this;
        int n = 0;
        while (tmp != NULL) {
            ++n;
            tmp = tmp->next;
        }
        return n;
    }

    bool LenGtEq(int n) {
        Instruction* tmp = this;
        int m = 0;
        while (tmp != NULL) {
            if (m++ >= n) {
                return true;
            }
            tmp = tmp->next;
        }
        return false;
    }

    Instruction* OffsetBy(int n) {
        Instruction* r = this;
        while (n-- > 0) {
            if (r == NULL) 
                return NULL;
            r = r->next;
        }
        return r;
    }

    bool IsDef() { 
        return opcode == op_reserved && !IsArg();
    }

	bool IsArg() {
		return opcode == op_reserved && prev != NULL && prev->IsInitializer();
	}

	bool IsDef(const std::string& var) { 
        return IsDef() && comment == var;
    }

    bool IsVar() {
        return IsGetVar() || IsSetVar();
    }

    bool IsPushOp() {
        switch (opcode)
        {
        case op_push_short:
        case op_push_int:
        case op_push_char:
        case op_push_byte:
        case op_push_sub:
        case op_push_neg1:
        case op_push_flt:
        case op_push0:
        case op_push1:
        case op_push2:
        case op_push_true:
        case op_push_false:
        case op_push_string:
            return true;
        default:
            return false;
        }
    }

    bool IsGetVar() {
        switch (opcode) 
        {
        case op_dup:
        case op_getN:
        case op_get1:
        case op_get2:
        case op_get3:
        case op_get4:
            return true;
        default:
            return false;
        }
    }

    bool IsSetVar() {
        switch (opcode) 
        {
        case op_popd:
        case op_setN:
        case op_set1:
        case op_set2:
        case op_set3:
        case op_set4:
            return true;
        default:
            return false;
        }
    }

    void SetOpCode(OpCodeEnum code, int arg = -1, std::string _comment = "") {
        opcode = code;
        operand = arg;
        comment = "";
    }

    void SetOpCode(OpCodeEnum code, Subroutine* sub, std::string _comment = "") {
        opcode = code;
        operand = reinterpret_cast<int>(sub);
        comment = "";
    }

    void SetNoop() {
        SetOpCode(op_noop);
    }

	bool IsSubRef() {
		return opcode == op_push_sub;
	}

    bool IsSubRefTo(Subroutine* sub) {
        return opcode == op_push_sub && GetSub() == sub;
    }

	bool IsCallTo(Subroutine* sub) {
		return IsSubRefTo(sub) && next != NULL && next->opcode == op_apply;
	}
	
    int GetHashCode() const {
        return (opcode << 8) + operand + 1;
    }

    bool Equals(const Instruction& i) {
        return GetHashCode() == i.GetHashCode();
    }

    int GetWidth() {
        return GetOpCodeWidth(opcode);
    }

    bool IsReturnStatement() {
        return opcode == op_noop && comment == "return";
    }

    Instruction* Lookahead(int n) {
        Instruction* i = next;
        while (i != NULL && n-- > 0)
            i = i->next;
        return i;
    }

    bool LookAheadMatches(int n, OpCodeEnum code) {
        Instruction* i = Lookahead(n);
        if (i == NULL) 
            return false;
        return i->opcode == code;
    }

    void CopyFrom(Instruction* i) {
        opcode = i->opcode;
        operand = i->operand;
        comment = i->comment;
    }

    bool IsBegin() {
        return opcode == op_noop && comment == "begin";
    }

    bool IsEnd() {
        return opcode == op_noop && comment == "end";
    }

	bool IsInitializer() {
		return opcode == op_noop && comment == "initializer";
	}

	bool IsUnlet() {
		return opcode == op_noop && comment == "unlet";
	}

	bool IsStackModifier() {
		return opcode == op_noop && comment == "stack_modifier";
	}

	bool IsStatement() {
		return opcode == op_noop && comment == "statement";
	}

	bool IsNoop() {
		return opcode == op_noop || opcode == op_reserved;
	}

	Subroutine* GetSub() {
		return reinterpret_cast<Subroutine*>(operand);
	}

	bool Matches(const Instruction& i) const {
		return opcode == i.opcode && operand == i.operand;
	}

	bool operator==(const Instruction& i) const {		
		return Matches(i);
	}

	std::string ToString() {
		std::string r = OpCodeToStr(opcode);
		if (operand != -1) {
			r += "(" + ToStr(operand) + ")";
		}
		return r;
	}

	bool IsPushInstruction() {
		switch (opcode) {
			case op_push_int:
			case op_push_char:
			case op_push_flt:
			case op_push_neg1:
			case op_push0:
			case op_push1:
			case op_push2:
			case op_push_sub:
			case op_push_sub_short:
			case op_nil:
			case op_null:
			case op_push_sub0 : 
			case op_push_sub1 : 
			case op_push_sub2 : 
			case op_push_sub3 : 
			case op_push_sub4 : 
			case op_push_sub5 : 
			case op_push_sub6 : 
			case op_push_sub7 : 
			case op_push_sub8 : 
			case op_push_sub9 : 
			case op_push_sub10 : 
			case op_push_sub11 : 
			case op_push_sub12 : 
			case op_push_sub13 : 
			case op_push_sub14 : 
			case op_push_sub15 : 
				return true;
			default:
					return true;
				return false;
		}
	}

	bool IsTerminator() {
		return IsSubTerminator(opcode);
	}
};

struct InstrSeq
{
    Instruction* begin;
    Instruction* end;

    InstrSeq(Instruction* _begin, Instruction* _end) 
        : begin(_begin), end(_end)
    {
    }

    InstrSeq(Instruction* _begin, int offset = -1) 
        : begin(_begin), end(offset == -1 ? NULL : _begin->OffsetBy(offset))
    {
    }

    InstrSeq(const InstrSeq& x) 
        : begin(x.begin), end(x.end)
    { 
    }

    int GetWidth() {
        int w = 0;
        for (Instruction* i = begin; i != end; i = i->next) 
            w += i->GetWidth();
        return w;
    }

    bool MatchesWithoutOverlap(InstrSeq seq) {
        if (Overlaps(seq))
            return false;
        Instruction* i = begin;
        Instruction* j = seq.begin;
        for (; i != end && j != seq.end; i = i->next, j = j->next) {
            if (i->GetHashCode() != j->GetHashCode())
                return false;
        }
        if (i != end || j != seq.end)
            return false;
        return true;
    }

    bool ContainsOp(OpCodeEnum op) {
        for (Instruction* i = begin; i != end; i = i->next) 
            if (i->opcode == op)
                return true;
        return false;
    }

    int Len() {
        int r = 0;
        Instruction* i = begin;
        while (i != end) {
            ++r;
            i = i->next;
        }
        return r;
    }

    bool InRange(Instruction* x) {
        Instruction* i = begin;
        while (i != x) {
            if (i == end) 
                return false;
            i = i->next;
        }
        return true;
    }

    bool Overlaps(InstrSeq seq) {
        return InRange(seq.begin) || seq.InRange(begin);
    }

	void DeleteBegin() {
		begin = begin->next;
		delete begin->prev;
		begin->prev = NULL;
	}

	void DeleteEnd() {
		end = end->prev;
		delete end->next;
		end->next = NULL;
	}

	bool Empty() {
		return begin == NULL;
	}

	InstrSeq Tail() {
		return InstrSeq(begin->next, end);
	}
};

#endif
